﻿//
// Copyright 2011 Kazuki Oikawa
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace MsgPack.Compiler {
    public static class EmitExtensions {
        public static void EmitLd(this ILGenerator il, Variable v) {
            switch (v.VarType) {
                case VariableType.Arg:
                    EmitLdarg(il, v);
                    break;
                case VariableType.Local:
                    EmitLdloc(il, v);
                    break;
                default:
                    throw new ArgumentException();
            }
        }

        public static void EmitLd(this ILGenerator il, params Variable[] list) {
            for (int i = 0; i < list.Length; i++)
                EmitLd(il, list[i]);
        }

        public static void EmitLdarg(this ILGenerator il, Variable v) {
            if (v.VarType != VariableType.Arg)
                throw new ArgumentException();

            switch (v.Index) {
                case 0: il.Emit(OpCodes.Ldarg_0); return;
                case 1: il.Emit(OpCodes.Ldarg_1); return;
                case 2: il.Emit(OpCodes.Ldarg_2); return;
                case 3: il.Emit(OpCodes.Ldarg_3); return;
            }
            if (v.Index <= byte.MaxValue) {
                il.Emit(OpCodes.Ldarg_S, (byte)v.Index);
            } else if (v.Index <= short.MaxValue) {
                il.Emit(OpCodes.Ldarg, v.Index);
            } else {
                throw new FormatException();
            }
        }

        public static void EmitLdloc(this ILGenerator il, Variable v) {
            if (v.VarType != VariableType.Local)
                throw new ArgumentException();

            switch (v.Index) {
                case 0: il.Emit(OpCodes.Ldloc_0); return;
                case 1: il.Emit(OpCodes.Ldloc_1); return;
                case 2: il.Emit(OpCodes.Ldloc_2); return;
                case 3: il.Emit(OpCodes.Ldloc_3); return;
            }
            if (v.Index <= byte.MaxValue) {
                il.Emit(OpCodes.Ldloc_S, (byte)v.Index);
            } else if (v.Index <= short.MaxValue) {
                il.Emit(OpCodes.Ldloc, v.Index);
            } else {
                throw new FormatException();
            }
        }

        public static void EmitSt(this ILGenerator il, Variable v) {
            switch (v.VarType) {
                case VariableType.Arg:
                    EmitStarg(il, v);
                    break;
                case VariableType.Local:
                    EmitStloc(il, v);
                    break;
                default:
                    throw new ArgumentException();
            }
        }

        public static void EmitStarg(this ILGenerator il, Variable v) {
            if (v.VarType != VariableType.Arg)
                throw new ArgumentException();

            if (v.Index <= byte.MaxValue) {
                il.Emit(OpCodes.Starg_S, (byte)v.Index);
            } else if (v.Index <= short.MaxValue) {
                il.Emit(OpCodes.Starg, v.Index);
            } else {
                throw new FormatException();
            }
        }

        public static void EmitStloc(this ILGenerator il, Variable v) {
            if (v.VarType != VariableType.Local)
                throw new ArgumentException();

            switch (v.Index) {
                case 0: il.Emit(OpCodes.Stloc_0); return;
                case 1: il.Emit(OpCodes.Stloc_1); return;
                case 2: il.Emit(OpCodes.Stloc_2); return;
                case 3: il.Emit(OpCodes.Stloc_3); return;
            }
            if (v.Index <= byte.MaxValue) {
                il.Emit(OpCodes.Stloc_S, (byte)v.Index);
            } else if (v.Index <= short.MaxValue) {
                il.Emit(OpCodes.Stloc, v.Index);
            } else {
                throw new FormatException();
            }
        }

        public static void EmitLdc(this ILGenerator il, int v) {
            switch (v) {
                case 0: il.Emit(OpCodes.Ldc_I4_0); return;
                case 1: il.Emit(OpCodes.Ldc_I4_1); return;
                case 2: il.Emit(OpCodes.Ldc_I4_2); return;
                case 3: il.Emit(OpCodes.Ldc_I4_3); return;
                case 4: il.Emit(OpCodes.Ldc_I4_4); return;
                case 5: il.Emit(OpCodes.Ldc_I4_5); return;
                case 6: il.Emit(OpCodes.Ldc_I4_6); return;
                case 7: il.Emit(OpCodes.Ldc_I4_7); return;
                case 8: il.Emit(OpCodes.Ldc_I4_8); return;
                case -1: il.Emit(OpCodes.Ldc_I4_M1); return;
            }
            if (v <= sbyte.MaxValue && v >= sbyte.MinValue) {
                il.Emit(OpCodes.Ldc_I4_S, (sbyte)v);
            } else {
                il.Emit(OpCodes.Ldc_I4, v);
            }
        }

        public static void EmitLd_False(this ILGenerator il) {
            il.Emit(OpCodes.Ldc_I4_1);
        }

        public static void EmitLd_True(this ILGenerator il) {
            il.Emit(OpCodes.Ldc_I4_1);
        }

        public static void EmitLdstr(this ILGenerator il, string v) {
            il.Emit(OpCodes.Ldstr, v);
        }

        public static void EmitLdMember(this ILGenerator il, MemberInfo m) {
            if (m.MemberType == MemberTypes.Field) {
                il.Emit(OpCodes.Ldfld, (FieldInfo)m);
            } else if (m.MemberType == MemberTypes.Property) {
                il.Emit(OpCodes.Callvirt, ((PropertyInfo)m).GetGetMethod(true));
            } else {
                throw new ArgumentException();
            }
        }

        public static void EmitStMember(this ILGenerator il, MemberInfo m) {
            if (m.MemberType == MemberTypes.Field) {
                il.Emit(OpCodes.Stfld, (FieldInfo)m);
            } else if (m.MemberType == MemberTypes.Property) {
                il.Emit(OpCodes.Callvirt, ((PropertyInfo)m).GetSetMethod(true));
            } else {
                throw new ArgumentException();
            }
        }
    }
}
